package com.wiscamp.ninechapters.common.rest;

import com.wiscamp.ninechapters.common.core.CollectionHelper;
import com.wiscamp.ninechapters.common.exception.*;
import com.wiscamp.ninechapters.ddd.PagedEntities;
import com.wiscamp.ninechapters.ddd.PagedQuery;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.function.*;

/**
 * API端口帮助类
 *
 * @author yifeng
 */
public class ResourceHelper {

    private static final Logger logger = LoggerFactory.getLogger(ResourceHelper.class);

    private ResourceHelper() {
    }

    static final String RESOURCE_PREFIX_LINE = "--- Resource Exception Occurs ---";

    // region version 2
    public static <T> RestResponse<T> getById(HttpServletRequest request, HttpServletResponse response, long id,
                                              LongFunction<T> getFunction) {
        RestResponse<T> restResponse;
        try {
            T entity = getFunction.apply(id);
            restResponse = getOkResponse(request, response, entity);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }

    public static <T> RestResponse<T> getListById(HttpServletRequest request, HttpServletResponse response, long id,
                                                  LongFunction<T> getFunction) {
        RestResponse<T> restResponse;
        try {
            T entities = getFunction.apply(id);
            restResponse = getOkResponse(request, response, entities);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }

    public static <T> RestResponse<T> getByText(HttpServletRequest request, HttpServletResponse response,
                                                String text, Function<String, T> function) {
        RestResponse<T> restResponse;
        try {
            T entity = function.apply(text);
            restResponse = getOkResponse(request, response, entity);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }

    public static <T> RestResponse<T> getByTextAndId(HttpServletRequest request,
                                                     HttpServletResponse response, String text, long id, BiFunction<String, Long, T> getFunction) {
        RestResponse<T> restResponse;
        try {
            T entity = getFunction.apply(text, id);
            restResponse = getOkResponse(request, response, entity);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }


    public static <T> RestResponse<T> getAll(HttpServletRequest request, HttpServletResponse response,
                                             Supplier<T> supplier) {
        RestResponse<T> restResponse;
        try {
            T entities = supplier.get();
            restResponse = getOkResponse(request, response, entities);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }

    public static <T> RestResponse<Collection<T>> getListByText(HttpServletRequest request,
                                                                HttpServletResponse response, String text, Function<String, Collection<T>> getFunction) {
        RestResponse<Collection<T>> restResponse;
        try {
            var entities = getFunction.apply(text);
            restResponse = getOkResponse(request, response, entities);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }

    public static <T> RestResponse<Collection<T>> getListByTextAndId(HttpServletRequest request,
                                                                     HttpServletResponse response, String text, long id, BiFunction<String, Long, Collection<T>> getFunction) {
        RestResponse<Collection<T>> restResponse;
        try {
            var entities = getFunction.apply(text, id);
            restResponse = getOkResponse(request, response, entities);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }

    public static <T> RestResponse<Collection<T>> getListByIdAndId(HttpServletRequest request,
                                                                   HttpServletResponse response, long id1, long id2, BiFunction<Long, Long, Collection<T>> getFunction) {
        RestResponse<Collection<T>> restResponse;
        try {
            var entities = getFunction.apply(id1, id2);
            restResponse = getOkResponse(request, response, entities);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }

    public static <T> RestResponse<Collection<T>> getPagedEntitiesByQuery(HttpServletRequest request,
                                                                          HttpServletResponse response, PagedQuery query,
                                                                          Function<PagedQuery, PagedEntities<T>> function) {
        RestResponse<Collection<T>> restResponse;
        try {
            var pagedEntities = function.apply(query);
            var meta = new Meta(pagedEntities.getPage(), pagedEntities.getPageSize(), pagedEntities.getPages(),
                    pagedEntities.getTotalCount(), pagedEntities.getSortDirection(), pagedEntities.getSortProperty());
            restResponse = new RestResponse<>("OK", "OK", pagedEntities.getEntities());
            restResponse.setMeta(meta);
            response.setStatus(200);
            response.setContentType("application/json; charset=utf-8");
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }

    public static <T, K> RestResponse<Collection<T>> getPagedEntities(HttpServletRequest request,
                                                                      HttpServletResponse response, Pagination<K> pagination,
                                                                      Function<Pagination<K>, PagedEntity<T>> function) {
        RestResponse<Collection<T>> restResponse;
        try {
            var pagedEntities = function.apply(pagination);
            Meta meta = new Meta(pagination.getPage(), pagination.getPerpage(), pagedEntities.getPages(),
                    pagedEntities.getTotal(), pagedEntities.getSort(), pagedEntities.getField());
            restResponse = new RestResponse<>("OK", "OK", pagedEntities.getEntities(), meta);
            response.setStatus(200);
            response.setContentType("application/json; charset=utf-8");
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }


    public static <C, V> RestResponse<V> createOrUpdate(HttpServletRequest request, HttpServletResponse response, C command,
                                                        Function<C, V> saveFunction) {
        RestResponse<V> restResponse;
        try {
            var entity = saveFunction.apply(command);
            restResponse = getOkResponse(request, response, entity);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }

    public static <V> RestResponse<V> enableOrDisable(HttpServletRequest request, HttpServletResponse response, Long id,
                                                      LongFunction<V> function) {
        RestResponse<V> restResponse;
        try {
            var entity = function.apply(id);
            restResponse = getOkResponse(request, response, entity);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }

    public static RestResponse<Boolean> delete(HttpServletRequest request, HttpServletResponse response, long id,
                                               LongFunction<Boolean> deleteFunction) {
        RestResponse<Boolean> restResponse;
        try {
            var result = deleteFunction.apply(id);
            restResponse = getOkResponse(request, response, result);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }
    // endregion


    public static <T> RestResponse<T> getById(HttpServletRequest request, HttpServletResponse response, long id,
                                              Logger logger, LongFunction<T> function) {
        RestResponse<T> restResponse;
        try {
            T entity = function.apply(id);
            restResponse = getOkResponse(request, response, entity);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }

    public static <T> RestResponse<T> getByText(HttpServletRequest request, HttpServletResponse response,
                                                String text, Logger logger, Function<String, T> function) {
        RestResponse<T> restResponse;
        try {
            T entity = function.apply(text);
            restResponse = getOkResponse(request, response, entity);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }

    public static <T> RestResponse<T> getByTextAndId(HttpServletRequest request,
                                                     HttpServletResponse response, String text, long id, Logger logger, BiFunction<String, Long, T> function) {
        RestResponse<T> restResponse;
        try {
            T entity = function.apply(text, id);
            restResponse = getOkResponse(request, response, entity);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }

    public static <T> RestResponse<T> getAll(HttpServletRequest request, HttpServletResponse response, Logger logger,
                                             Supplier<T> supplier) {
        RestResponse<T> restResponse;
        try {
            T entities = supplier.get();
            restResponse = getOkResponse(request, response, entities);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }

    public static <T> RestResponse<T> getListById(HttpServletRequest request, HttpServletResponse response, long id,
                                                  Logger logger, LongFunction<T> function) {
        RestResponse<T> restResponse;
        try {
            T entities = function.apply(id);
            restResponse = getOkResponse(request, response, entities);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }

    public static RestResponse<Boolean> delete(HttpServletRequest request, HttpServletResponse response, long id,
                                               Logger logger, LongFunction<Boolean> function) {
        RestResponse<Boolean> restResponse;
        try {
            Boolean result = function.apply(id);
            restResponse = getOkResponse(request, response, result);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }

    public static <T> RestResponse<T> save(HttpServletRequest request, HttpServletResponse response, T entity,
                                           Logger logger, UnaryOperator<T> function) {
        RestResponse<T> restResponse;
        try {
            T newEntity = function.apply(entity);
            restResponse = getOkResponse(request, response, newEntity);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }

    public static <V, C> RestResponse<V> create(HttpServletRequest request, HttpServletResponse response, C createCommand,
                                                Function<C, V> function) {
        RestResponse<V> restResponse;
        try {
            var vo = function.apply(createCommand);
            restResponse = getOkResponse(request, response, vo);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }

    public static <V, C> RestResponse<V> update(HttpServletRequest request, HttpServletResponse response, C updateCommand,
                                                Function<C, V> function) {
        RestResponse<V> restResponse;
        try {
            var vo = function.apply(updateCommand);
            restResponse = getOkResponse(request, response, vo);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }

    public static <T, K> RestResponse<Collection<T>> getPagedEntities(HttpServletRequest request,
                                                                      HttpServletResponse response, Pagination<K> pagination, Logger logger,
                                                                      Function<Pagination<K>, PagedEntity<T>> function) {
        RestResponse<Collection<T>> restResponse;
        try {
            PagedEntity<T> pagedEntities = function.apply(pagination);
            Meta meta = new Meta(pagination.getPage(), pagination.getPerpage(), pagedEntities.getPages(),
                    pagedEntities.getTotal(), pagedEntities.getSort(), pagedEntities.getField());
            restResponse = new RestResponse<>("OK", "OK", pagedEntities.getEntities(), meta);
            response.setStatus(200);
            response.setContentType("application/json; charset=utf-8");
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex, logger);
        }
        return restResponse;
    }


    // region toVO
    public static <E, V> RestResponse<V> getById(HttpServletRequest request, HttpServletResponse response, long id,
                                                 LongFunction<E> function, Function<E, V> toVOFunction) {
        RestResponse<V> restResponse;
        try {
            var entity = function.apply(id);
            V convertEntity = null;
            if (entity != null)
                convertEntity = toVOFunction.apply(entity);
            restResponse = getOkResponse(request, response, convertEntity);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }


    public static <E, V> RestResponse<V> getByText(HttpServletRequest request, HttpServletResponse response,
                                                   String text, Function<String, E> function, Function<E, V> toVOFunction) {
        RestResponse<V> restResponse = null;
        try {
            var entity = function.apply(text);
            V convertEntity = null;
            if (entity != null)
                convertEntity = toVOFunction.apply(entity);
            restResponse = getOkResponse(request, response, convertEntity);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }

    public static <E, V> RestResponse<V> getByTextAndId(HttpServletRequest request,
                                                        HttpServletResponse response, String text, long id,
                                                        BiFunction<String, Long, E> function, Function<E, V> toVOFunction) {
        RestResponse<V> restResponse = null;
        try {
            var entity = function.apply(text, id);
            V convertEntity = null;
            if (entity != null)
                convertEntity = toVOFunction.apply(entity);
            restResponse = getOkResponse(request, response, convertEntity);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }

    public static <E, V> RestResponse<Collection<V>> getAll(HttpServletRequest request, HttpServletResponse response,
                                                            Supplier<Collection<E>> supplier, Function<E, V> toVOFunction) {
        RestResponse<Collection<V>> restResponse = null;
        try {
            var entities = supplier.get();
            var convertEntities = CollectionHelper.convertToArrayList(entities, toVOFunction);
            restResponse = getOkResponse(request, response, convertEntities);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }

    public static <E, V> RestResponse<Collection<V>> getListById(HttpServletRequest request, HttpServletResponse response, long id,
                                                                 LongFunction<Collection<E>> function, Function<E, V> toVOEntityFunction) {
        RestResponse<Collection<V>> restResponse = null;
        try {
            var entities = function.apply(id);
            var convertEntities = CollectionHelper.convertToArrayList(entities, toVOEntityFunction);
            restResponse = getOkResponse(request, response, convertEntities);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }

    public static <E, V> RestResponse<Collection<V>> getListByText(HttpServletRequest request,
                                                                   HttpServletResponse response, String text, Function<String, Collection<E>> function,
                                                                   Function<E, V> toVOFunction) {
        RestResponse<Collection<V>> restResponse = null;
        try {
            var entities = function.apply(text);
            var convertEntities = CollectionHelper.convertToArrayList(entities, toVOFunction);
            restResponse = getOkResponse(request, response, convertEntities);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }

    public static <E, V> RestResponse<Collection<V>> getListByTextAndId(HttpServletRequest request,
                                                                        HttpServletResponse response, String text, long id, BiFunction<String, Long, Collection<E>> function,
                                                                        Function<E, V> toVOFunction) {
        RestResponse<Collection<V>> restResponse;
        try {
            var entities = function.apply(text, id);
            var convertEntities = CollectionHelper.convertToArrayList(entities, toVOFunction);
            restResponse = getOkResponse(request, response, convertEntities);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }

    public static <E, Q, V> RestResponse<Collection<V>> getPagedEntities(HttpServletRequest request,
                                                                         HttpServletResponse response, Pagination<Q> pagination,
                                                                         Function<Pagination<Q>, PagedEntity<E>> function, Function<E, V> toVOFunction) {
        RestResponse<Collection<V>> restResponse;
        try {
            PagedEntity<E> pagedEntities = function.apply(pagination);
            Meta meta = new Meta(pagination.getPage(), pagination.getPerpage(), pagedEntities.getPages(),
                    pagedEntities.getTotal(), pagedEntities.getSort(), pagedEntities.getField());

            var entities = pagedEntities.getEntities();
            var voList = CollectionHelper.convertToArrayList(entities, toVOFunction);
            restResponse = new RestResponse<>("OK", "OK", voList, meta);
            response.setStatus(200);
            response.setContentType("application/json; charset=utf-8");
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }

    public static <E, V> RestResponse<V> save(HttpServletRequest request, HttpServletResponse response, V viewObject,
                                              UnaryOperator<E> saveFunction, Function<E, V> toVOFunction, Function<V, E> toEntityFunction) {
        RestResponse<V> restResponse;
        try {
            var entity = toEntityFunction.apply(viewObject);
            V vo = null;
            if (entity != null) {
                var savedEntity = saveFunction.apply(entity);
                vo = toVOFunction.apply(savedEntity);
            }
            restResponse = getOkResponse(request, response, vo);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }

    public static <C, E, V> RestResponse<V> createOrUpdate(HttpServletRequest request, HttpServletResponse response, C command,
                                                           UnaryOperator<E> saveFunction, Function<E, V> toVOFunction, Function<C, E> toEntityFunction) {
        RestResponse<V> restResponse;
        try {
            var entity = toEntityFunction.apply(command);
            V vo = null;
            if (entity != null) {
                var savedEntity = saveFunction.apply(entity);
                vo = toVOFunction.apply(savedEntity);
            }
            restResponse = getOkResponse(request, response, vo);
            response.flushBuffer();
        } catch (Exception ex) {
            restResponse = getExceptionResponse(request, response, ex);
        }
        return restResponse;
    }

    // endregion


    // region OkResponse & ExceptionResponse
    public static <T> RestResponse<T> getOkResponse(HttpServletRequest request, HttpServletResponse response, T result) {
        RestResponse<T> restResponse = new RestResponse<>("OK", "OK", result);
        response.setStatus(200);
        response.setContentType("application/json; charset=utf-8");
        return restResponse;
    }

    public static <T> RestResponse<T> getExceptionResponse(HttpServletRequest request, HttpServletResponse response,
                                                           Exception exception, Logger logger) {
        return getExceptionResponse(exception, logger);
    }

    public static <T> RestResponse<T> getExceptionResponse(HttpServletRequest request, HttpServletResponse response,
                                                           Exception exception) {
        return getExceptionResponse(exception);
    }

    public static <T> RestResponse<T> getExceptionResponse(Exception exception) {
        return getExceptionResponse(exception, logger);
    }

    public static <T> RestResponse<T> getExceptionResponse(Exception exception, Logger logger) {
        String code = "UNKNOWN";
        String message = exception.getMessage();
        String stackTrace = ExceptionHandler.getStackTrace(exception);
        if (exception instanceof DataException) {
            var sex = (DataException) exception;
            code = sex.getCode();
            message = ExceptionCodeHelper.getInstance().getDataExceptionMessage(code);
        } else if (exception instanceof ServiceException) {
            var sex = (ServiceException) exception;
            code = sex.getCode();
            message = ExceptionCodeHelper.getInstance().getServiceExceptionMessage(code);
        } else if (exception instanceof AppServiceException) {
            var sex = (AppServiceException) exception;
            code = sex.getCode();
            message = ExceptionCodeHelper.getInstance().getAppServiceExceptionMessage(code);
        } else if (exception instanceof Exception) {
            ExceptionHandler.logException(exception, logger, RESOURCE_PREFIX_LINE);
            message = "An unknown resource exception occurs.";
        }
        return new RestResponse<>(code, message, null, stackTrace);
    }
    // endregion
}
